package com.amumu.drama.common.utils.http;

import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.HttpConnectionFactory;
import org.apache.http.conn.ManagedHttpClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultHttpResponseParserFactory;
import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.impl.io.DefaultHttpRequestWriterFactory;
import org.apache.http.ssl.SSLContexts;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;

/**
 * http 请求工具类
 * @author sunzhuang
 * @date 2023/1/12 8:32
 * @slogan You build it , You run it.
 */
public class HttpClientUtils {

    public static CloseableHttpClient createHttpClient() {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(2 * 1000)//连接超时设置为2s。建立TCP连接的时间
                .setSocketTimeout(5 * 1000)//等待数据超时设置为5s。等待服务器返回数据的时间
                .setConnectionRequestTimeout(2 * 1000)//从连接池获取连接的等待超时时间设置为2s
                .build();
        //设置重试次数，默认为3次；当前是禁用掉
        DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler(0, false);
        return createHttpClient(50, 30, requestConfig, retryHandler);
    }

    public static CloseableHttpClient createHttpClient(int maxTotal, int defaultMaxPerRoute, RequestConfig defaultRequestConfig, HttpRequestRetryHandler retryHandler) {
        //注册访问协议相关的Socket工厂
        //SSLContext sslContext = SSLContext.getInstance("TLS");

        SSLContext sslContext = null;
        try {
            sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            throw new RuntimeException("createHttpClient error", e);
        }

        SSLConnectionSocketFactory sslcsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);

        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", sslcsf).build();

        //HttpConnectionFactory:配置写请求/解析响应处理器
        HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connectionFactory = new ManagedHttpClientConnectionFactory(
                DefaultHttpRequestWriterFactory.INSTANCE,
                DefaultHttpResponseParserFactory.INSTANCE
        );

        //DNS解析器
        DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE;
        //创建连接池管理器
        PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, connectionFactory, dnsResolver);
        //设置默认的socket参数
        manager.setDefaultSocketConfig(SocketConfig.custom().setTcpNoDelay(true).build());
        manager.setMaxTotal(maxTotal);//设置最大连接数。高于这个值时，新连接请求，需要阻塞，排队等待
        //路由是对MaxTotal的细分。
        //每个路由实际最大连接数默认值是由DefaultMaxPerRoute控制。
        //MaxPerRoute设置的过小，无法支持大并发：ConnectionPoolTimeoutException:Timeout waiting for connection from pool
        manager.setDefaultMaxPerRoute(defaultMaxPerRoute);//每个路由的最大连接
        manager.setValidateAfterInactivity(5 * 1000);//在从连接池获取连接时，连接不活跃多长时间后需要进行一次验证，默认为2s

        CloseableHttpClient closeableHttpClient = HttpClients.custom()
                .setConnectionManager(manager)
                .setConnectionManagerShared(false)//连接池不是共享模式，这个共享是指与其它httpClient是否共享
                .evictIdleConnections(60L, TimeUnit.SECONDS)//定期回收空闲连接
                .evictExpiredConnections()//回收过期连接
                .setConnectionTimeToLive(60, TimeUnit.SECONDS)//连接存活时间，如果不设置，则根据长连接信息决定
                .setDefaultRequestConfig(defaultRequestConfig)//设置默认的请求参数
                .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE)//连接重用策略，即是否能keepAlive
                .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)//长连接配置，即获取长连接生产多长时间
                .setRetryHandler(retryHandler)
                .build();

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                closeableHttpClient.close();
            } catch (IOException ignored) {
            }
        }));
        return closeableHttpClient;
    }
}
